package cn.s1995.sloth.bizs.exception;


import cn.hutool.core.util.StrUtil;
import cn.s1995.sloth.common.core.constant.enums.hint.Hint;
import cn.s1995.sloth.common.core.util.R;
import cn.s1995.sloth.common.idempotent.exception.IdempotentException;
import com.mysql.cj.jdbc.exceptions.MySQLTimeoutException;
import io.lettuce.core.RedisCommandTimeoutException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.client.RedisTimeoutException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import java.net.SocketTimeoutException;
import java.util.concurrent.TimeoutException;

/**
 * 全局异常处理器
 *
 * @author penf
 * @date 2022/05/24
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理 Exception 异常
     *
     * @param e 异常
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public R exceptionHandler(Exception e) {
        log.error("服务异常 - ", e);
        if (e instanceof TimeoutException
                || e instanceof MySQLTimeoutException
                || e instanceof SocketTimeoutException
                || e instanceof RedisTimeoutException
                || e instanceof RedisCommandTimeoutException) {
            return R.failed(Hint.SYSTEM_BUSY);
        }
        R rest = R.failed(Hint.INTERNAL_SERVER_ERROR);
        if (StrUtil.isNotEmpty(e.getLocalizedMessage())) {
            rest.setMsg(e.getLocalizedMessage());
        }

        return rest;
    }

    /**
     * 处理 BizException 异常
     *
     * @param e 异常
     * @return
     */
    @ExceptionHandler(value = BizException.class)
    public R businessExceptionHandler(BizException e) {
        log.error("业务异常 - {}", e.getHintEnum().getMsg());
        return R.builder()
                .code(e.getHintEnum().getCode())
                .msg(e.getHintEnum().getMsg())
                .build();
    }

    /**
     * 处理请求参数校验异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({
            MethodArgumentNotValidException.class,
            MethodArgumentTypeMismatchException.class,
            ValidationException.class,
            BindException.class,
            MissingServletRequestParameterException.class,
            MaxUploadSizeExceededException.class,
            UnsatisfiedServletRequestParameterException.class,
    })
    public R badRequestExceptionHandler(Exception e) {
        log.error("请求参数校验异常 - ", e);
        R rest = R.failed(Hint.BAD_REQUEST);
        if (e instanceof MethodArgumentNotValidException) {
            rest.setMsg(getErrorMsg(((MethodArgumentNotValidException) e).getBindingResult()));
        } else if (e instanceof BindException) {
            rest.setMsg(getErrorMsg(((BindException) e).getBindingResult()));
        } else {
            if (StrUtil.isNotEmpty(e.getMessage())) {
                rest.setMsg(e.getMessage());
            }
        }
        return rest;
    }

    /**
     * 处理请求的参数有误异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({IllegalArgumentException.class,})
    public R badParameterExceptionHandler(IllegalArgumentException e) {
        log.error("请求的参数有误异常 - ", e);
        R rest = R.failed(Hint.BAD_PARAMETER);
        if (StrUtil.isNotEmpty(e.getMessage())) {
            rest.setMsg(e.getMessage());
        }
        return rest;
    }

    /**
     * 处理授权拒绝异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(AccessDeniedException.class)
    public R accessDeniedException(AccessDeniedException e) {
        // 如果程序抛出了AccessDeniedException但是当前认证状态是匿名的（未认证），
        // 那么会ExceptionTranslationFilter会抛出InsufficientAuthenticationException。
        // 而所有的AuthenticationException会被配置的AuthenticationEntryPoint实现类（RestAuthenticationEntryPoint）捕获。
        // 所以通过抛出AccessDeniedException进入自定义AccessDeniedHandler（RestfulAccessDeniedHandler）的前提是当前已完成身份认证
        // 此方法用于捕获未完成身份认证的授权拒绝异常
        log.error("未完成身份认证的授权拒绝异常 - ", e);
        R rest = R.failed(Hint.FORBIDDEN);
        if (StrUtil.isNotEmpty(e.getMessage())) {
            rest.setMsg(e.getMessage());
        }
        return rest;
    }

    /**
     * 处理BadCredentialsException异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({BadCredentialsException.class})
    public R accessDeniedException(BadCredentialsException e) {
        log.error("错误身份异常 - ", e);
        R rest = R.failed(Hint.UNAUTHORIZED);
        if (StrUtil.isNotEmpty(e.getMessage())) {
            rest.setMsg(e.getMessage());
        }
        return rest;
    }


    /**
     * 处理请求幂等异常
     * IdempotentException: 重复请求，请稍后重试
     *
     * @param e
     * @return
     */
    @ExceptionHandler({IdempotentException.class})
    public R idempotentExceptionHandler(IdempotentException e) {
        log.error("请求请求幂等异常 - {}", e.getLocalizedMessage());
        R rest = R.failed();
        if (StrUtil.isNotEmpty(e.getMessage())) {
            rest.setMsg(e.getMessage());
        }
        return rest;
    }

    /**
     * 获取错误消息
     *
     * @param bindingResult
     * @return
     */
    private String getErrorMsg(BindingResult bindingResult) {
        StringBuilder errorInfo = new StringBuilder("");
        for (int i = 0; i < bindingResult.getFieldErrors().size(); i++) {
            if (i > 0) {
                errorInfo.append(", ");
            }
            FieldError fieldError = bindingResult.getFieldErrors().get(i);
            // errorInfo.append(fieldError.getField()).append(": ").append(fieldError.getDefaultMessage());
            errorInfo.append(fieldError.getDefaultMessage());
        }
        return errorInfo.toString();
    }
}
